﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using BomberManServer.Gaming.Object;
using BomberManServer.Gaming.Serialization.Object.Connection;
using BomberManServer.Gaming.Serialization;
using System.Net;

namespace BomberManServer.Gaming.Socket
{
    public class ConnectionWatcher : InGame
    {

        public enum ConnectionState { CONNECTED, DISCONNECTED, READY }

        // the connection to watch
        TcpListener listener;
        // the players to look after
        public List<PlayerSocket> connected { get; private set;}
        List<PlayerSocket> disconnected;
        // unknown clients
        List<TcpClient> unidentified;
        Serializer serializer;
        List<IPAddress> blacklisted;

        static SocketAsyncEventArgs eventargs = new SocketAsyncEventArgs();

        public ConnectionWatcher(Game game) : base(game)
        {
            this.listener = game.thread.socket;

            this.disconnected = new List<PlayerSocket>();
            foreach(Player player in game.match.players)
                this.disconnected.Add(new PlayerSocket(player));

            this.connected = new List<PlayerSocket>();
            this.unidentified = new List<TcpClient>();
            blacklisted = new List<IPAddress>();
            serializer = new Serializer();
        }

        public void compute()
        {
            List<PlayerSocket> to_disconnect = new List<PlayerSocket>();

            TcpClient client;
            // look if connections are pendings
            while (listener.Pending())
            {
                // accept the connection
                client = listener.AcceptTcpClient();
                /*
                if (blacklisted.Contains(((IPEndPoint)client.Client.RemoteEndPoint).Address))
                {
                    client.Client.DisconnectAsync(eventargs);
                    continue;
                }
                 * */
                // add it to unidentified list
                unidentified.Add(client);
                // set socket to non blocking
                client.Client.Blocking = true;
            }

            // look if player has disconnected
            foreach(PlayerSocket player in connected) {
                if (!player.active)
                    to_disconnect.Add(player);
                else player.ageing();
            }
            
            foreach(PlayerSocket player in to_disconnect)
                disconnect(player);

            // check if unknown client has sent the key
            identify();
            
        }


        // check if unknown client has sent the key
        private void identify()
        {
            if (unidentified.Count == 0) return;
            List<TcpClient> identified = new List<TcpClient>();
            foreach (TcpClient client in unidentified)
            {
                // if there are some datas
                if (client.Client.Available == 0)
                    continue;
                // create stream
                NetworkStream netStream = new NetworkStream(client.Client, true);

                // we check first byte to be the Message.SendKey header 
                if ((Message)netStream.ReadByte() == Message.SendKey)
                {
                    // extract the data
                    SendKey sendKey;
                    serializer.DeSerialize(netStream,out sendKey);

                    // try to connect the client
                    if (connect(sendKey.key, client))
                    {
                        identified.Add(client);
                        // if ok we skip blacklisting
                        continue;
                    }
                }

                // we dont know him
                // or sends random stuff
                // so we ban him
                blacklist(client.Client);
                identified.Add(client);
            }


            foreach (TcpClient client in identified)
                unidentified.Remove(client);

            identified.Clear();
        }

        // blacklist an IP adress so he cant connect
        private void blacklist(System.Net.Sockets.Socket socket)
        {
            //blacklisted.Add(((IPEndPoint)socket.RemoteEndPoint).Address);
            socket.DisconnectAsync(eventargs);
        }

        // returns true if the token exists
        private bool connect(UInt32 token, TcpClient client)
        {
            foreach (PlayerSocket p in disconnected)
            {
                if (p.player.compareToken(token))
                {
                    // check for double registration
                    if (p.connected)
                        break;

                    // register the client
                    PlayerSocket player = p;
                    p.connect(client);
                    disconnected.Remove(p);
                    connected.Add(p);
                    playerConnected(p);
                    if (disconnected.Count == 0) allConnected();
                    return true;
                }
            }
            return false;
        }

        // disconnect a player
        public void disconnect(PlayerSocket player)
        {
            player.disconnect();
            connected.Remove(player);
            disconnected.Add(player);
            playerDisconnected(player);
        }

        private void playerConnected(PlayerSocket player)
        {
            // the player knows the others
            game.sender.onMapInitialize(player);
            // the player knows himself
            game.sender.onIdentified(player);
        }

        private void playerDisconnected(PlayerSocket player)
        {
            //kill the bastard
        }

        private void allConnected()
        {
            //game.ruleKeeper.nextAction();
        }
    }
}
